<?php

namespace system\core;

use system\exceptions\NullArgumentException;
use system\exceptions\NullTemplateException;
use system\exceptions\TransformationFailException;
/**
 * This class is the basis(not a base class) of the entire document.
 *
 * Contains object model of the entire document and the associated visual
 * presentation (XSL).
 *
 * The Document Object Model in the context of this class
 * represents the array of {@link DocumentComponent}. Each of the components may contain
 * any amount of both the components and fields, so this model can be easily
 * converted to XML. Using of XSL transformations allows easily transform into any
 * format, both data and presentation.
 *
 * In order to define a visual representation
 * of the document requires a single root XSL template, and any number of
 * included templates. By default, the root template called 'generic.xsl' and
 * located in directory 'application/views/'.
 *
 * To include any XSL template in the root template user can take advantage of two
 * ways, first is to use the method {@link addViewItem()} or specify path to the
 * template in constructor of {@link DocumentComponent}.
 *
 * In order to convert the document object model using the previously specified
 * templates, use the method {@link transform ()}
 * 
 * @see DocumentComponent
 * @package system\core
 * @author Pavel Egorov {@link mailto:pavel@egorov.ru pavel@egorov.ru}
 * @version 0.0.1 $Revision: 50 $
 * @copyright Copyright (c) 2011, Pavel Egorov
 * @license http://www.gnu.org/licenses/gpl-3.0.txt GNU GENERAL PUBLIC LICENSE
 */
final class Document {

    private $dataItems;
    private $viewItems;
    private $rootView;
    private $cwd;

    /**
     * Creates an instance of Document and setup default template and current
     * working directory
     *
     * @param String $currentWorkingDirectory Current working directory has NULL
     * by default
     */
    public function __construct($currentWorkingDirectory = null) {
        $this->rootView = 'generic';
        if ($currentWorkingDirectory == NULL) {
            $this->cwd = getcwd();
        }
        $this->cwd = $currentWorkingDirectory;
    }

    /**
     * Sets ROOT template
     *
     * @param String $strMainTemplateName path to XSL template
     */
    public function setRootView($strMainTemplateName) {
        $this->rootView = $strMainTemplateName;
    }

    /**
     * Adds {@link DocumentComponent} to {@link Document} and specify XSL
     * template for associated component
     *
     * @param DocumentComponent $component
     * @param String $strView
     */
    public function add($component, $strView = null) {
        if ($component != null) {
            $this->addDataItem($component);
            if ($strView != null) {
                $this->addViewItem($strView);
            }
        } else {
            throw new NullArgumentException();
        }
    }

    /**
     * Returns XML document
     * 
     * @return DOMDocument
     */
    public function getXml() {
        return $this->getDataDocument();
    }

    /**
     * Transforms XML document using XSL to output
     * @return String
     */
    public function transform() {
        try{
            $xslt = new \XsltProcessor();
            $xml = $this->getDataDocument();
            $xsl = $this->getViewDocument();
            $xslt->importStylesheet($xsl);

            $html = $xslt->transformToXML($xml);
            return $html;
        }catch(\Exception $e){
            throw $e;
        }
    }

    /**
     * Adds {@link DocumentComponent} to {@link Document}
     * 
     * @param DocumentComponent $item
     */
    public function addDataItem($item) {
        $this->dataItems[] = $item;
    }

    /**
     * Adds path to XSL template to be included
     *
     * @param String $item
     */
    public function addViewItem($item) {
        $path = $this->pathToTemplate($item);
        if (file_exists($path)) {
            $this->viewItems[] = $path;
        }else{
            throw new NullTemplateException($item);
        }
    }

    /**
     * @access private
     */
    private function getDataDocument() {
        $doc = new \DomDocument('1.0', 'UTF-8');
        $root = $doc->createElement("root");
        $doc->appendChild($root);
        if (sizeof($this->dataItems) > 0) {
            $this->arrayToDoc($this->dataItems, $root, $doc);
        }
        $doc->formatOutput = true;
        return $doc;
    }

    /**
     * @access private
     */
    private function getViewDocument() {
        $doc = new \DomDocument('1.0');

        if ($this->rootView == NULL) {
            throw new NullTemplateException();
        }

        $doc->load($this->pathToTemplate($this->rootView));
        if ($this->viewItems != null) {
            foreach ($this->viewItems as $templateName) {
                $stylesheet = $doc->getElementsByTagName("stylesheet");
                $element = $doc->createElementNS('http://www.w3.org/1999/XSL/Transform', 'include');
                $element = $stylesheet->item(0)->appendChild($element);
                $element->setAttribute('href', $templateName);
            }
        }
        return $doc;
    }

    /**
     * @access private
     */
    private function pathToTemplate($templateName) {
        if ($templateName != NULL) {
            return str_replace('\\', '/', getcwd() . '/application/view/' . $templateName . '.xsl');
        } else {
            return NULL;
        }
    }

    /**
     * @access private
     */
    private function arrayToDoc($array, $parent, $doc) {
        foreach ($array as $key => $value) {
            if (is_object($value)) {
                $value = $value->toArray();
            }
            if (is_array($value)) {
                if (is_int($key)) {
                    $this->arrayToDoc($value, $parent, $doc);
                } elseif ($key != '__inner') {
                    if (is_int(strrpos($key, '\\'))) {
                        $key = substr($key, strrpos($key, '\\') + 1);
                    }
                    $element = $doc->createElement($key);
                    $parent->appendChild($element);
                    $this->arrayToDoc($value, $element, $doc);
                } else {
                    $this->arrayToDoc($value, $parent, $doc);
                }
            } else {
                if ($key != '__componentName' && $key != '__value' && $key != '__template') {
                    $element = $doc->createElement($key);
                    $parent->appendChild($element);
                    $textElement = $doc->createTextNode($value);
                    $element->appendChild($textElement);
                } elseif ($key == '__value') {
                    $textElement = $doc->createTextNode($value);
                    $parent->appendChild($textElement);
                } elseif ($key == '__template') {
                    $this->addViewItem($value);
                }
            }
        }
    }

}